﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;

namespace Microsoft.Office.OneNote
{
    internal sealed class OneNoteObjectProperties
    {
        [Flags]
        private enum EntrySource : uint
        {
            None = 0x0,
            Loaded = 0x1,
            Assigned = 0x2,
        }

        private sealed class Entry
        {
            /// <summary>
            /// The value in OneNote at the time the 
            /// object was loaded.
            /// </summary>
            private object _loadedValue;
            
            /// <summary>
            /// The value assigned by the API user.
            /// </summary>
            private object _assignedValue;
            
            /// <summary>
            /// 
            /// </summary>
            private EntrySource _source;

            public void Assign(object value)
            {
                if (!Equals(Value, value))
                {
                    _assignedValue = value;
                    _source |= EntrySource.Assigned;
                }
            }

            /// <summary>
            /// Clears the assigned value.
            /// </summary>
            public void Clear()
            {
                _assignedValue = null;
                _source &= ~EntrySource.Assigned;
            }

            public void Load(object value)
            {
                _loadedValue = value;
                _source |= EntrySource.Loaded;
            }

            public bool HasValue
            {
                get { return _source != EntrySource.None; }
            }

            public bool IsDirty
            {
                get { return (_source & EntrySource.Assigned) == EntrySource.Assigned; }
            }

            public object Value
            {
                get
                {
                    if (IsDirty)
                    {
                        return _assignedValue;
                    }

                    return _loadedValue;
                }
            }

            public object LoadedValue
            {
                get { return _loadedValue; }
            }
        }

        private readonly Dictionary<OneNoteProperty, Entry> _entries;
        private readonly OneNoteObject _owner;

        public OneNoteObjectProperties(OneNoteObject owner)
            : this(owner, new Dictionary<OneNoteProperty, Entry>())
        {
        }

        private OneNoteObjectProperties(OneNoteObject owner, Dictionary<OneNoteProperty, Entry> entries)
        {
            _owner = owner; 
            _entries = entries;
        }

        public Object GetValue(OneNoteProperty property)
        {
            CheckValidOrServerView();
            ValidateProperty(property);

            Entry entry = this[property];
            Object result;

            if (property.CheckFlagsAnd(OneNotePropertyFlags.RequiresGetValueCallback))
            {
                _owner.OnPropertyRequested(property);
            }

            if (IsServerView)
            {
                // get the value which came from OneNote

                result = entry.LoadedValue;

                OneNoteObject resultON = (result as OneNoteObject);
                if (resultON != null)
                {
                    // in case it's an OneNoteObject, return 
                    // its server view

                    result = OneNoteObject.GetServerView(resultON);
                }
                
                return result;
            }

            result = entry.Value;

            if (!entry.HasValue)
            {
                result = property.DefaultValue;

                DefaultValueProvider defValProvider = (result as DefaultValueProvider);
                if (defValProvider != null)
                {
                    result = defValProvider.GetValue();
                }

                entry.Load(result);
            }

            return result;
        }

        public void SetValue(OneNoteProperty property, Object value)
        {
            CheckValid();
            ValidateProperty(property);

            Entry entry = this[property];
            Object oldValue = entry.Value;

            entry.Assign(value);

            if (!Equals(oldValue, value))
            {
                _owner.SetFlags(true, OneNoteObjectFlags.Dirty);
                _owner.OnPropertyChanged(property, value, oldValue);
            }
        }

        internal void ClearValue(OneNoteProperty property)
        {
            _entries[property].Clear();
        }

        internal void DeserializeValue(OneNoteProperty property, Object value)
        {
            CheckValid();
            ValidateProperty(property);

            Entry entry = this[property];
            OneNoteObject innerObject = (value as OneNoteObject);

            if ((entry.IsDirty && !Equals(entry.LoadedValue, value)) || 
                (innerObject != null && innerObject.HasConflicts))
            {
                // OneNoteObject-typed property has a conflict
                HasConflicts = true;
            }

            entry.Load(value);
        }

        internal OneNoteProperty[] GetDirtyProperties()
        {
            CheckValid();

            return (from entry in _entries
                    where entry.Value.IsDirty
                    select entry.Key).ToArray();
        }

        internal OneNoteObjectProperties GetServerView(OneNoteObject serverView)
        {
            return new OneNoteObjectProperties(serverView, _entries);
        }

        private bool HasConflicts
        {
            set 
            {
                CheckValid();
                _owner.SetFlags(value, OneNoteObjectFlags.HasConflicts); 
            }
        }

        private bool IsServerView
        {
            get { return _owner.CheckFlagsAnd(OneNoteObjectFlags.IsServerView); }
        }

        private Entry this[OneNoteProperty property]
        {
            get
            {
                Entry entry;

                if (!_entries.TryGetValue(property, out entry))
                {
                    _entries[property] = entry = new Entry();
                }

                return entry;
            }
        }

        private void CheckValid()
        {
            _owner.CheckValid();
        }

        private void CheckValidOrServerView()
        {
            _owner.CheckValidOrServerView();
        }

        private void ValidateProperty(OneNoteProperty property)
        {
            if (property == null)
            {
                throw new ArgumentNullException("property");
            }

            if (!property.OwnerType.IsAssignableFrom(_owner.GetType()))
            {
                string message = string.Format(CultureInfo.CurrentUICulture,
                    ErrorMessages.UnknownProperty, property, _owner.GetType());

                throw new ArgumentException(message, "property");
            }
        }


    }
}
